using System;
using System.Collections.Generic;
using System.Text;
using DslFactory.Utilities.DteCodeModel;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Modeling;
using System.Net.Security;

namespace Dslfactory.wcfLanguage
{
 internal static class MessageContractIncrementalCodeGeneration
 {
  /// <summary>Fully qualified name of the MessageContractAttribute</summary>
  public static readonly string messageContractAttributeFullName = "System.ServiceModel.MessageContractAttribute";

  /// <summary>Fully qualified name of the MessageBodyMemberAttribute</summary>
  public static readonly string messageBodyMemberAttributeFullName = "System.ServiceModel.MessageBodyMemberAttribute";

  /// <summary>Fully qualified name of the MessageHeaderAttribute</summary>
  public static readonly string messageHeaderAttributeFullName = "System.ServiceModel.MessageHeaderAttribute";

  /// <summary>
  /// Subscribe to Interface code generation events
  /// </summary>
  /// <param name="Store"></param>
  /// <param name="eventManagerDirectory"></param>
  public static void SubsribeToStoreEvents(Store Store)
  {
   EventManagerDirectory eventManagerDirectory = Store.EventManagerDirectory;

   // When a field/property of the class changes, the [ContractAttribute] must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(MessageContract)),
                                                          new EventHandler<ElementPropertyChangedEventArgs>(EnsureContractAttribute));

   // When a field/property of the MessageMember / OperationContract changes, the [OperationContract] attribute must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(MessageMember)),
                                                               new EventHandler<ElementPropertyChangedEventArgs>(EnsureMessageMemberContractAttribute));

   // When a field/property is removed from an Interface, the corresponding code must be deleted. When one is added, code must be added
   eventManagerDirectory.ElementDeleted.Add(Store.DomainDataDirectory.GetDomainClass(typeof(MessageContractHasMessageMembers)),
                                                         new EventHandler<ElementDeletedEventArgs>(EnsuresMessageMemberRemoved));
   eventManagerDirectory.ElementAdded.Add(Store.DomainDataDirectory.GetDomainClass(typeof(MessageContractHasMessageMembers)),
                                                         new EventHandler<ElementAddedEventArgs>(EnsuresMessageMemberAdded));
  }



  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsureMessageMemberContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   SynchronizeCodeWithModel(arg.ModelElement as MessageMember);
  }

  /// <summary>
  /// Ensure an attribute for interfaces
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="arg"></param>
  private static void EnsureContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   MessageContract messageContract = arg.ModelElement as MessageContract;

   // Name
   if (arg.DomainProperty.Id == MessageContract.NameDomainPropertyId)
   {
    // Get the class
    string oldFullName = (string.IsNullOrEmpty(messageContract.Namespace) ? arg.OldValue as string : messageContract.Namespace + '.' + arg.OldValue as string);
    KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(messageContract.Store);
    CodeClass2 codeClass = knownCodeTypes.GetFullNamedType(oldFullName) as CodeClass2;
    CodeElement2 element = codeClass as CodeElement2;
    if (element != null)
    {
     element.RenameSymbol(arg.NewValue as string);
     knownCodeTypes.TakeIntoAccount(element as CodeType, true);
    }
   }

   else
    SynchronizeCodeWithModel(messageContract);
  }



  /// <summary>
  /// Synchronizes code from model for a MessageContract
  /// </summary>
  /// <param name="iface">Interface for which we want to update code</param>
  private static void SynchronizeCodeWithModel(MessageContract messageContract)
  {
   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(messageContract.Store);
   if (knownCodeTypes == null)
    return;

   // Get the class
   CodeClass2 codeClass2 = knownCodeTypes.GetFullNamedType(messageContract.FullName) as CodeClass2;
   if (codeClass2 != null)
   {
    // Get the attribute (if necessary)
    CodeAttribute2 attribute = IncrementalGenerator.EnsureAttributeIff(codeClass2, messageContractAttributeFullName, true) as CodeAttribute2;

    // Ensure the arguments if necessary
    List<string> domainPropertiesToAttributeArgumentsMapping = new List<string>(new string[] 
                                                                {
                                                                 "IsWrapped",
                                                                 "ProtectionLevel",
                                                                 "WrapperName",
                                                                 "WrapperNamespace"
                                                                });
    IncrementalGenerator.EnsureAttributeArgumentsFromDomainProperties(messageContract, attribute, domainPropertiesToAttributeArgumentsMapping);

    // Synchronize members
    foreach (MessageMember member in messageContract.MessageMembers)
     SynchronizeCodeWithModel(member);
   }
  }


  /// <summary>
  /// Updates the method code from the model
  /// </summary>
  /// <param name="method"></param>
  private static void SynchronizeCodeWithModel(MessageMember messageMember)
  {
   if (messageMember.MessageContract == null)
    return;

   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(messageMember.Store);
   if (knownCodeTypes == null)
    return;

   // Get the class
   CodeClass2 codeClass2 = knownCodeTypes.GetFullNamedType(messageMember.MessageContract.FullName) as CodeClass2;
   if (codeClass2 == null)
    return;

   CodeElement codeMessageMember = IncrementalGenerator.FindFirstMember(codeClass2, messageMember.Name);
   if (codeMessageMember == null)
    return;

   // Get the attribute (if necessary)
   CodeAttribute2 attribute = null;

   switch (messageMember.MessageMemberKind)
   {
    case MessageMemberKind.Normal:
     IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageBodyMemberAttributeFullName, false);
     IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageHeaderAttributeFullName, false);
     break;

    case MessageMemberKind.MessageBody:
     attribute = IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageBodyMemberAttributeFullName, true) as CodeAttribute2;
     IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageHeaderAttributeFullName, false);
     IncrementalGenerator.EnsureAttributeArgumentsFromDomainProperties(messageMember, attribute,
                                                                             new List<string>(new string[] 
                                                                             {
                                                                              "WCFName->Name",
                                                                              "WCFNamespace->Namespace",
                                                                              "Order",
                                                                              "ProtectionLevel"
                                                                             })
                                                                      );
    break;

    case MessageMemberKind.MessageHeader:
     IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageBodyMemberAttributeFullName, false);
     attribute = IncrementalGenerator.EnsureAttributeIff(codeMessageMember, messageHeaderAttributeFullName, true) as CodeAttribute2;
     IncrementalGenerator.EnsureAttributeArgumentsFromDomainProperties(messageMember, attribute,
                                                                             new List<string>(new string[] 
                                                                             {
                                                                              "Actor",
                                                                              "MustUnderstand",
                                                                              "WCFName->Name",
                                                                              "WCFNamespace->Namespace",
                                                                              "ProtectionLevel",
                                                                              "Relay",
                                                                              "EmitDefaultValue"
                                                                             })
                                                                      );

     break;
    case MessageMemberKind.MessageHeaderArray:
     // Not implemented yet
     break;

    default:
     break;
   }
  }


  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresMessageMemberRemoved(object sender, ElementEventArgs arg)
  {
   MessageContractHasMessageMembers dataContractHasMessageMember = arg.ModelElement as MessageContractHasMessageMembers;
   MessageMember messageMember = dataContractHasMessageMember.MessageMember;
   MessageContract messageContract = dataContractHasMessageMember.MessageContract;

   // Important : Do not remove methods when the interface is removed from diagram
   if ((messageContract.IsDeleting) || (messageContract.IsDeleted))
    return;

   CodeClass2 codeClass = KnownCodeTypes.FromStore(messageMember.Store).GetFullNamedType(messageContract.FullName) as CodeClass2;
   if (codeClass == null)
    return;
   CodeElement codeMessageMember = IncrementalGenerator.FindFirstMember(codeClass, messageMember.Name);
   if (codeMessageMember == null)
    return;
   IncrementalGenerator.RemoveOrCommentDataMember(codeMessageMember);
  }


  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresMessageMemberAdded(object sender, ElementEventArgs arg)
  {
   MessageContractHasMessageMembers dataContractHasMessageMember = arg.ModelElement as MessageContractHasMessageMembers;
   MessageMember messageMember = dataContractHasMessageMember.MessageMember;
   MessageContract messageContract = dataContractHasMessageMember.MessageContract;

   CodeClass2 codeInterface = KnownCodeTypes.FromStore(messageContract.Store).GetFullNamedType(messageContract.FullName) as CodeClass2;
   if (codeInterface == null)
    return;

   if (messageMember.Kind == DataMemberKind.Field)
    IncrementalGenerator.EnsureField(codeInterface, messageMember.Name, messageMember.MemberFullTypeName);
   else if (messageMember.Kind == DataMemberKind.Property)
    IncrementalGenerator.EnsureProperty(codeInterface, messageMember.Name, string.IsNullOrEmpty(messageMember.MemberFullTypeName)?"int":messageMember.MemberFullTypeName);

//   CodeElement codeMessageMember = IncrementalGenerator.EnsureMessageMember(codeInterface, messageMember.Name);
   SynchronizeCodeWithModel(messageMember);
  }


  internal static void Generate(TypeOfInterest type, CodeNamespace ns)
  {
   MessageContract messageContract = type as MessageContract;
   CodeClass2 codeClass = IncrementalGenerator.EnsureClass(ns, type.Name) as CodeClass2;
   KnownCodeTypes.FromStore(type.Store).TakeIntoAccount(codeClass as CodeType, false);

   DataClassIncrementalCodeGeneration.GenerateMembers(codeClass, messageContract);
   SynchronizeCodeWithModel(messageContract);
  }
 }
}
